import java.io.Serializable;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

/**
 * Represents a symmetric or assymetric key
 */
public class Key implements Serializable {
    
    /**
     * Enumeration of the types of keys available
     */
    public enum TypeOfKey {
        Symmetric,
        Asymmetric
    };
    
    /**
     * Enumeration of the types of encryption available
     */
    public enum TypeOfEncryption {
        DES,
        RSA,
        AES,
        TripleDES,
        DESede,
        RC2,
        RC4,
        Blowfish,
        ARCFOUR,
        Rijndael,
    };
    
    // Name of the key
    public String name;
    // Symmetric key object (if it is a symmetric key)
    public SecretKey symmetric_key;
    // Public part of an assymetric key
    public PublicKey public_key;
    // Private part of an assymetric key
    public PrivateKey private_key;
    // The type of key (symmetric or assymetric)
    public TypeOfKey type_key;
    // The used cypher
    public TypeOfEncryption type_encryption;
    
    /**
     * Class constructor
     * @param name Name of the used key
     * @param type_key Type of key used (symmetric or asymmetric)
     * @param type_encryption Encryption cypher used
     */
    public Key(String name, TypeOfKey type_key, TypeOfEncryption type_encryption) {
        this.name = name;
        this.type_key = type_key;
        this.type_encryption = type_encryption;
        String encryption_name = new String("");
        try {
            if(type_key == TypeOfKey.Symmetric)
                this.symmetric_key = KeyGenerator.getInstance(Key.typeEncryption2String(type_encryption)).generateKey();
            else {
                KeyPairGenerator generator = KeyPairGenerator.getInstance(Key.typeEncryption2String(type_encryption));
                generator.initialize(1024);
                KeyPair pair = generator.genKeyPair();
                this.private_key = pair.getPrivate();
                this.public_key = pair.getPublic();
            }
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }
    }
    
    /**
     * Returns the public key in hexadecimal format
     * @return Hexadecimal representation of the public key, in a string
     */
    public String getPublicKey() {
        if(this.type_key == TypeOfKey.Symmetric)
            return Utilities.byteArrayToHexString(this.symmetric_key.getEncoded());
        else
            if(this.public_key != null)
                return Utilities.byteArrayToHexString(this.public_key.getEncoded());
            else
                return "";
    }
    
    /**
     * Returns the private key in hexadecimal format
     * @return Hexadecimal representation of the private key, in a string
     */
    public String getPrivateKey() {
        if(this.type_key == TypeOfKey.Asymmetric && this.private_key != null)
            return Utilities.byteArrayToHexString(this.private_key.getEncoded());
        else
            return "";
    }
    
    /**
     * Transforms the string representation of the a cypher to it's equivalent enumeration
     * @param str The name of the cypher in a string
     * @return Enumeration of the type of encryption, corresponding to the given string representation of the encryption scheme
     */
    public static TypeOfEncryption string2TypeEncryption(String str) {
        if(str.equals("DES"))
            return TypeOfEncryption.DES;
        if(str.equals("AES"))
            return TypeOfEncryption.AES;
        if(str.equals("RSA"))
            return TypeOfEncryption.RSA;
        if(str.equals("TripleDES"))
            return TypeOfEncryption.TripleDES;
        if(str.equals("DESede"))
            return TypeOfEncryption.DESede;
        if(str.equals("RC2"))
            return TypeOfEncryption.RC2;
        if(str.equals("RC4"))
            return TypeOfEncryption.RC4;
        if(str.equals("Blowfish"))
            return TypeOfEncryption.Blowfish;
        if(str.equals("ARCFOUR"))
            return TypeOfEncryption.ARCFOUR;
        if(str.equals("Rijndael"))
            return TypeOfEncryption.Rijndael;

        return null;
    }
    
    /**
     * Transforms the enumeration representation of the a cypher to it's equivalent string
     * @param enc The enumeration of the cypher
     * @return String representation of an encryption scheme, given by the provided enumeration constant
     */
    public static String typeEncryption2String(TypeOfEncryption enc) {
        if(enc == TypeOfEncryption.DES)
            return new String("DES");
        if(enc == TypeOfEncryption.AES)
            return new String("AES");
        if(enc == TypeOfEncryption.RSA)
            return new String("RSA");
        if(enc == TypeOfEncryption.TripleDES)
            return new String("TripleDES");
        if(enc == TypeOfEncryption.DESede)
            return new String("DESede");
        if(enc == TypeOfEncryption.RC2)
            return new String("RC2");
        if(enc == TypeOfEncryption.RC4)
            return new String("RC4");
        if(enc == TypeOfEncryption.Blowfish)
            return new String("Blowfish");
        if(enc == TypeOfEncryption.ARCFOUR)
            return new String("ARCFOUR");
        if(enc == TypeOfEncryption.Rijndael)
            return new String("Rijndael");
        
        return null;
    }
    
    /**
     * Returns the size of the encrypted block using the current cypher
     * @return The size of an encrypted data block after applying the key's encryption algorithm to a chunk of data.
     */
    public int getEncryptedBlockSize() {
        if(this.type_encryption == TypeOfEncryption.RSA)
            return 128;
        if(this.type_encryption == TypeOfEncryption.DES ||
                this.type_encryption == TypeOfEncryption.TripleDES  ||
                this.type_encryption == TypeOfEncryption.DESede ||
                this.type_encryption == TypeOfEncryption.RC2 ||
                this.type_encryption == TypeOfEncryption.Blowfish)
            return 104;
        if(this.type_encryption == TypeOfEncryption.AES || this.type_encryption == TypeOfEncryption.Rijndael)
            return 112;
        if(this.type_encryption == TypeOfEncryption.RC4 || this.type_encryption == TypeOfEncryption.ARCFOUR)
            return 100;
        
        return 0;
    }
}
